package com.zmj.sy.mom.srv.aps.bean.bo;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zmj.sy.mom.srv.aps.bean.entity.order.ApsDetailWeekday;
import com.zmj.sy.mom.srv.aps.bean.entity.order.ApsWorkDetail;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.*;
import com.zmj.sy.mom.srv.aps.mapper.ApsDetailWeekdayMapper;
import com.zmj.sy.mom.srv.aps.mapper.ApsWorkDetailMapper;
import com.zmj.sy.mom.srv.aps.utils.MkBeanUtils;
import com.zmj.sy.mom.srv.aps.utils.ProjectConstants;
import com.zmj.sy.mom.srv.aps.utils.SyExceptionUtils;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Getter
@Setter
public class ApsFactoryCache {

    private String factoryCode;

    private List<Customer> customers;

    private List<Group> groups;

//    private List<StationGroup> stationGroups;

    private List<Station> stations;

    private Map<String, Phase> phaseCodeMap;

//    private List<StationWeekdayScheme> stationWeekdaySchemes;

    private List<ShiftType> shiftTypeList;

    private List<WeekdayRule> stationWeekdayRules;

    private Map<String, List<Station>> masterStationMap;

    private List<ShiftType> shiftTypes;

    private List<Shift> shifts;

    private List<ApsMachine> machines;

    private Map<String, List<ApsMachine>> groupCodeMachineMap;

    private List<BreakPeriod> breakPeriods;

    private LocalDate machineStartDate;

    private LocalDate machineEndDate;

    private List<String> ignorePhaseCode;

    private Boolean preview;

    // 瓶颈工序
    private List<String> bottleneckPhaseCode;


    private String pzCode;
    private Integer pzSecond;

    /**
     * 你已经是一个成熟的程序员了，你应该知道这个方法是干嘛的。
     * 如果你不知道也无伤大雅，为了维护世界和平，我告诉你 这是给工位增加日历的方法。
     *
     * @param startDate 从哪个时间增加
     * @param day       增加多少天
     */
    public void addDay(LocalDate startDate, int day) {

        if (CollUtil.isEmpty(machines)) {
            throw SyExceptionUtils.e("没有初始化设备信息");
        }


        if(machineStartDate == null){
            machineStartDate = startDate;
            machineEndDate = startDate.plusDays(day);
        } else {
            startDate = machineEndDate.plusDays(1);
            machineEndDate = startDate.plusDays(day);
        }

        LocalDate endDate = startDate.plusDays(day);



//        Map<Integer, StationWeekdayScheme> stationSchemeMap = weekdaySchemes.stream().collect(Collectors.toMap(StationWeekdayScheme::getStationId, s -> s));

        Map<Integer, List<WeekdayRule>> stationRuleMap = stationWeekdayRules.stream().collect(Collectors.groupingBy(WeekdayRule::getShiftTypeId));
        Map<Integer, List<Shift>> shiftMap = shifts.stream().collect(Collectors.groupingBy(Shift::getShiftTypeId));
        Map<Integer, List<BreakPeriod>> breakPeriodMap = breakPeriods.stream().collect(Collectors.groupingBy(BreakPeriod::getShiftId));


        for (ApsMachine machine : machines) {
//            StationWeekdayScheme scheme = stationSchemeMap.get(machine.getId());
            List<WeekdayRule> ruleList = stationRuleMap.get(machine.getStation().getShiftTypeId());
//            if (scheme == null) {
//                throw SyExceptionUtils.e("工位日历信息不完整");
//            }
//
//            if(startDate.isBefore(scheme.getStartDate()) || endDate.isAfter(scheme.getEndDate())){
//                throw SyExceptionUtils.e("设备时间链超出工位日历范围");
//            }
            if(machine.getWeekDays() == null){
                machine.setWeekDays(new ArrayList<>());
            }
            List<Shift> machineShift = shiftMap.get(machine.getStation().getShiftTypeId());

            List<ApsWeekDay> allWeekday = Stream
                    .iterate(startDate, s -> s.plusDays(1))
                    .limit(day + 1L)
                    .flatMap(e ->
                            machineShift.stream()
                                    .map(shift -> {
                                        ApsWeekDay newDay = new ApsWeekDay();
                                        MkBeanUtils.copyPropertiesIgnore(shift, newDay);
                                        newDay.setSchemeId(shift.getShiftTypeId());
                                        newDay.setShiftDay(e);
                                        newDay.setShiftSeq(shift.getSeq());
                                        newDay.setShiftId(shift.getId());
                                        newDay.setStartDateTime(LocalDateTime.of(e.plusDays(shift.getOffsetStartDay()), shift.getStartTime()));
                                        newDay.setEndDateTime(LocalDateTime.of(e.plusDays(shift.getOffsetEndDay()), shift.getEndTime()));
                                        return newDay;
                                    })
                                    .flatMap(newDay -> {

                                        List<BreakPeriod> bp = breakPeriodMap.getOrDefault(newDay.getShiftId(), new ArrayList<>());
                                        List<ApsWeekDay> all = new ArrayList<>();
                                        for (BreakPeriod breakPeriod : bp) {
                                            if(LocalDateTime.of(e.plusDays(breakPeriod.getOffsetStartDay()), breakPeriod.getStartTime()).isAfter(newDay.getEndDateTime())){
                                                continue;
                                            }
                                            if(LocalDateTime.of(e.plusDays(breakPeriod.getOffsetEndDay()), breakPeriod.getEndTime()).isBefore(newDay.getStartDateTime())){
                                                continue;
                                            }
                                            ApsWeekDay apsWeekDay = MkBeanUtils.copyProperties(newDay, ApsWeekDay.class);
                                            apsWeekDay.setEndDateTime(LocalDateTime.of(e.plusDays(breakPeriod.getOffsetStartDay()), breakPeriod.getStartTime()));
                                            all.add(apsWeekDay);
                                            newDay.setStartDateTime(LocalDateTime.of(e.plusDays(breakPeriod.getOffsetEndDay()), breakPeriod.getEndTime()));
                                        }
                                        all.add(newDay);
                                        return all.stream();
                                    })


                    )
                    .sorted(Comparator.comparing(ApsWeekDay::getStartDateTime))
                    .collect(Collectors.toList());



            for (ApsWeekDay w : allWeekday) {
                for (WeekdayRule weekdayRule : ruleList) {
                    if (weekdayRule.getType() == 4) {
                        if (weekdayRule.getValue().equals(String.valueOf(w.getShiftDay().getDayOfWeek().getValue()))) {
                            w.setDateType(weekdayRule.getDateType());
                            w.setWorkState(weekdayRule.getStatus());
                            w.setWeekdayRuleId(weekdayRule.getId());
                        }
                    } else if (weekdayRule.getType() == 1){
                        String value = weekdayRule.getValue();
                        String[] split = value.split(ProjectConstants.WEEKDAY_RULE_WEEKVALUE_SPLIT);
                        LocalDate start = LocalDate.parse(split[0], DatePattern.NORM_DATE_FORMATTER);
                        LocalDate end = LocalDate.parse(split[1], DatePattern.NORM_DATE_FORMATTER);
                        if (!w.getShiftDay().isBefore(start) && !w.getShiftDay().isAfter(end)) {
                            w.setDateType(weekdayRule.getDateType());
                            w.setWorkState(weekdayRule.getStatus());
                            w.setText(weekdayRule.getText());
                            w.setWeekdayRuleId(weekdayRule.getId());
                        }

                    } else if (weekdayRule.getType() == 3) {
                        if (LocalDate.parse(weekdayRule.getValue(), DatePattern.NORM_DATE_FORMATTER).equals(w.getShiftDay())) {
                            w.setDateType(weekdayRule.getDateType());
                            w.setWorkState(weekdayRule.getStatus());
                            w.setText(weekdayRule.getText());
                            w.setWeekdayRuleId(weekdayRule.getId());
                        }
                    } else if (weekdayRule.getType() == 2) {
                        String value = weekdayRule.getValue();
                        String[] split = value.split(ProjectConstants.WEEKDAY_RULE_WEEKVALUE_SPLIT);
                        if (split[0].equals(w.getShiftDay().toString()) && split[1].equals(String.valueOf(w.getShiftId()))) {
                            w.setDateType(weekdayRule.getDateType());
                            w.setWorkState(weekdayRule.getStatus());
                            w.setText(weekdayRule.getText());
                            w.setWeekdayRuleId(weekdayRule.getId());
                        }
                    }
                }
            }


            List<ApsWeekDay> collect = allWeekday.stream().filter(w -> w.getWorkState().equals(1)).collect(Collectors.toList());

            machine.getWeekDays().addAll(collect);
        }


    }

    public void insertDay(int day, ApsDetailWeekdayMapper apsDetailWeekdayMapper, ApsWorkDetailMapper apsWorkDetailMapper){
        LocalDate endDate = machineStartDate.minusDays(day);
        insertDay(day);

        // 生产工单占位填充
        LambdaQueryWrapper<ApsDetailWeekday> wrapper = Wrappers.lambdaQuery();
        wrapper.ge(ApsDetailWeekday::getShiftDay, endDate.minusDays(day));
        wrapper.le(ApsDetailWeekday::getShiftDay, endDate);
        wrapper.ge(ApsDetailWeekday::getState, 2);
        wrapper.ne(ApsDetailWeekday::getState, 4);
        wrapper.in(ApsDetailWeekday::getOrderType, 1, 3, 4);
        wrapper.in(ApsDetailWeekday::getStationId, stations.stream().map(Station::getId).distinct().collect(Collectors.toList()));
        wrapper.orderByAsc(ApsDetailWeekday::getPlanStartDateTime);
        List<ApsDetailWeekday> apsDetailWeekdays = apsDetailWeekdayMapper.selectList(wrapper);

        if(CollUtil.isNotEmpty(apsDetailWeekdays)){
            List<Integer> collect1 = apsDetailWeekdays.stream().map(ApsDetailWeekday::getApsWorkDetailId).distinct().collect(Collectors.toList());
            Map<Integer, ApsWorkDetail> detailMap = apsWorkDetailMapper.selectBatchIds(collect1)
                    .stream()
                    .collect(Collectors.toMap(ApsWorkDetail::getId, Function.identity()));

            insertTasks(apsDetailWeekdays, detailMap);
        }
    }

    public void insertDay(int day) {
        LocalDate endDate = machineStartDate.minusDays(1);
        LocalDate startDate = machineStartDate.minusDays(day);
        if (CollUtil.isEmpty(machines)) {
            throw SyExceptionUtils.e("没有初始化设备信息");
        }

        machineStartDate = startDate;


//        if(machineStartDate == null){
//            machineStartDate = startDate;
//            machineEndDate = startDate.plusDays(day);
//        } else {
//            startDate = machineEndDate.plusDays(1);
//            machineEndDate = startDate.plusDays(day);
//        }


//        Map<Integer, StationWeekdayScheme> stationSchemeMap = stationWeekdaySchemes.stream().collect(Collectors.toMap(StationWeekdayScheme::getStationId, s -> s));
        Map<Integer, List<WeekdayRule>> stationRuleMap = stationWeekdayRules.stream().collect(Collectors.groupingBy(WeekdayRule::getShiftTypeId));
        Map<Integer, List<Shift>> shiftMap = shifts.stream().collect(Collectors.groupingBy(Shift::getShiftTypeId));
        Map<Integer, List<BreakPeriod>> breakPeriodMap = breakPeriods.stream().collect(Collectors.groupingBy(BreakPeriod::getShiftId));


        for (ApsMachine machine : machines) {
//            StationWeekdayScheme scheme = stationSchemeMap.get(machine.getId());
            List<WeekdayRule> ruleList = stationRuleMap.get(machine.getStation().getShiftTypeId());
//            if (scheme == null) {
//                throw SyExceptionUtils.e("工位日历信息不完整");
//            }
//
//            if(startDate.isBefore(scheme.getStartDate()) || endDate.isAfter(scheme.getEndDate())){
//                throw SyExceptionUtils.e("设备时间链超出工位日历范围");
//            }
            if(machine.getWeekDays() == null){
                machine.setWeekDays(new ArrayList<>());
            }
            List<Shift> machineShift = shiftMap.get(machine.getStation().getShiftTypeId());

            List<ApsWeekDay> allWeekday = Stream
                    .iterate(startDate, s -> s.plusDays(1))
                    .limit(day + 1L)
                    .flatMap(e ->
                            machineShift.stream()
                                    .map(shift -> {
                                        ApsWeekDay newDay = new ApsWeekDay();
                                        MkBeanUtils.copyPropertiesIgnore(shift, newDay);
                                        newDay.setSchemeId(shift.getShiftTypeId());
                                        newDay.setShiftDay(e);
                                        newDay.setShiftSeq(shift.getSeq());
                                        newDay.setShiftId(shift.getId());
                                        newDay.setStartDateTime(LocalDateTime.of(e.plusDays(shift.getOffsetStartDay()), shift.getStartTime()));
                                        newDay.setEndDateTime(LocalDateTime.of(e.plusDays(shift.getOffsetEndDay()), shift.getEndTime()));
                                        return newDay;
                                    })
                                    .flatMap(newDay -> {

                                        List<BreakPeriod> bp = breakPeriodMap.get(newDay.getSchemeId());
                                        List<ApsWeekDay> all = new ArrayList<>();
                                        for (BreakPeriod breakPeriod : bp) {
                                            if(LocalDateTime.of(e.plusDays(breakPeriod.getOffsetStartDay()), breakPeriod.getStartTime()).isAfter(newDay.getEndDateTime())){
                                                continue;
                                            }
                                            if(LocalDateTime.of(e.plusDays(breakPeriod.getOffsetEndDay()), breakPeriod.getEndTime()).isBefore(newDay.getStartDateTime())){
                                                continue;
                                            }
                                            ApsWeekDay apsWeekDay = MkBeanUtils.copyProperties(newDay, ApsWeekDay.class);
                                            apsWeekDay.setEndDateTime(LocalDateTime.of(e.plusDays(breakPeriod.getOffsetStartDay()), breakPeriod.getStartTime()));
                                            all.add(apsWeekDay);
                                            newDay.setStartDateTime(LocalDateTime.of(e.plusDays(breakPeriod.getOffsetEndDay()), breakPeriod.getEndTime()));
                                        }
                                        all.add(newDay);
                                        return all.stream();
                                    })

                    )
                    .filter( e->e.getStartDateTime() != null)
                    .sorted(Comparator.comparing(ApsWeekDay::getStartDateTime))
                    .collect(Collectors.toList());


            for (ApsWeekDay w : allWeekday) {
                for (WeekdayRule weekdayRule : ruleList) {
                    if (weekdayRule.getType() == 4) {
                        if (weekdayRule.getValue().equals(String.valueOf(w.getShiftDay().getDayOfWeek().getValue()))) {
                            w.setDateType(weekdayRule.getDateType());
                            w.setWorkState(weekdayRule.getStatus());
                            w.setWeekdayRuleId(weekdayRule.getId());
                        }
                    } else if (weekdayRule.getType() == 1){
                        String value = weekdayRule.getValue();
                        String[] split = value.split(ProjectConstants.WEEKDAY_RULE_WEEKVALUE_SPLIT);
                        LocalDate start = LocalDate.parse(split[0], DatePattern.NORM_DATE_FORMATTER);
                        LocalDate end = LocalDate.parse(split[1], DatePattern.NORM_DATE_FORMATTER);
                        if (!w.getShiftDay().isBefore(start) && !w.getShiftDay().isAfter(end)) {
                            w.setDateType(weekdayRule.getDateType());
                            w.setWorkState(weekdayRule.getStatus());
                            w.setText(weekdayRule.getText());
                            w.setWeekdayRuleId(weekdayRule.getId());
                        }

                    } else if (weekdayRule.getType() == 3) {
                        if (LocalDate.parse(weekdayRule.getValue(), DatePattern.NORM_DATE_FORMATTER).equals(w.getShiftDay())) {
                            w.setDateType(weekdayRule.getDateType());
                            w.setWorkState(weekdayRule.getStatus());
                            w.setText(weekdayRule.getText());
                            w.setWeekdayRuleId(weekdayRule.getId());
                        }
                    } else if (weekdayRule.getType() == 2) {
                        String value = weekdayRule.getValue();
                        String[] split = value.split(ProjectConstants.WEEKDAY_RULE_WEEKVALUE_SPLIT);
                        if (split[0].equals(w.getShiftDay().toString()) && split[1].equals(String.valueOf(w.getShiftId()))) {
                            w.setDateType(weekdayRule.getDateType());
                            w.setWorkState(weekdayRule.getStatus());
                            w.setText(weekdayRule.getText());
                            w.setWeekdayRuleId(weekdayRule.getId());
                        }
                    }
                }
            }


            List<ApsWeekDay> collect = allWeekday.stream().filter(w -> w.getWorkState().equals(1)).collect(Collectors.toList());
            collect.addAll(machine.getWeekDays());
            machine.setWeekDays(collect);
        }


    }

    public void addDay(int day , ApsDetailWeekdayMapper apsDetailWeekdayMapper, ApsWorkDetailMapper apsWorkDetailMapper) {
        LocalDate startDate = machineEndDate.plusDays(1);
        addDay(null, day);

        // 生产工单占位填充
        LambdaQueryWrapper<ApsDetailWeekday> wrapper = Wrappers.lambdaQuery();
        wrapper.ge(ApsDetailWeekday::getShiftDay, startDate);
        wrapper.le(ApsDetailWeekday::getShiftDay, startDate.plusDays(day));
        wrapper.ge(ApsDetailWeekday::getState, 2);
        wrapper.ne(ApsDetailWeekday::getState, 4);
        wrapper.in(ApsDetailWeekday::getOrderType, 1, 3, 4);
        wrapper.in(ApsDetailWeekday::getStationId, stations.stream().map(Station::getId).distinct().collect(Collectors.toList()));
        wrapper.orderByAsc(ApsDetailWeekday::getPlanStartDateTime);
        List<ApsDetailWeekday> apsDetailWeekdays = apsDetailWeekdayMapper.selectList(wrapper);

        if(CollUtil.isNotEmpty(apsDetailWeekdays)){
            List<Integer> collect1 = apsDetailWeekdays.stream().map(ApsDetailWeekday::getApsWorkDetailId).distinct().collect(Collectors.toList());
            Map<Integer, ApsWorkDetail> detailMap = apsWorkDetailMapper.selectBatchIds(collect1)
                    .stream()
                    .collect(Collectors.toMap(ApsWorkDetail::getId, Function.identity()));

            addTasks(apsDetailWeekdays, detailMap);
        }
    }

    public void addTasks(List<ApsDetailWeekday> apsDetailWeekdays, Map<Integer, ApsWorkDetail> detailMap) {
//        if(Boolean.TRUE.equals(preview)){
//            return ;
//        }
        Map<Integer, List<ApsDetailWeekday>> stationGroupMap = apsDetailWeekdays
                .stream()
                .collect(Collectors.groupingBy(ApsDetailWeekday::getStationId));

        for (ApsMachine machine : machines) {
            List<ApsDetailWeekday> apsDetailWeekdays1 = stationGroupMap.get(machine.getId());
            if (CollUtil.isEmpty(apsDetailWeekdays1)) {
                continue;
            }

            ApsStep last = machine;
            while(last.nextTask != null){
                last = last.nextTask;
            }

            Map<Integer, List<ApsDetailWeekday>> collect = apsDetailWeekdays1.stream().collect(Collectors.groupingBy(ApsDetailWeekday::getApsWorkDetailId));

            Set<Map.Entry<Integer, List<ApsDetailWeekday>>> entries = collect.entrySet();

            List<ApsTask> l = new ArrayList<>();
            for (Map.Entry<Integer, List<ApsDetailWeekday>> entry : entries) {
                ApsWorkDetail apsWorkDetail = detailMap.get(entry.getKey());

                entry.getValue().sort(Comparator.comparing(ApsDetailWeekday::getPlanStartDateTime));
                if(apsWorkDetail == null){
                    apsWorkDetail = new ApsWorkDetail();
                }
                apsWorkDetail.setPlanStartDateTime(entry.getValue().get(0).getPlanStartDateTime());
                apsWorkDetail.setPlanEndDateTime(entry.getValue().get(entry.getValue().size() - 1).getPlanEndDateTime());

                ApsTask task = new ApsTask(apsWorkDetail);
                l.add(task);

            }

            l.sort(Comparator.comparing(ApsTask::getPlanStartDateTime));
            for (ApsTask task : l) {
                last.setNextTask(task);
                last = task;
            }

        }
    }


    public void insertTasks(List<ApsDetailWeekday> apsDetailWeekdays, Map<Integer, ApsWorkDetail> detailMap) {
//        if(Boolean.TRUE.equals(preview)){
//            return ;
//        }
        Map<Integer, List<ApsDetailWeekday>> stationGroupMap = apsDetailWeekdays
                .stream()
                .collect(Collectors.groupingBy(ApsDetailWeekday::getStationId));

        for (ApsMachine machine : machines) {
            List<ApsDetailWeekday> apsDetailWeekdays1 = stationGroupMap.get(machine.getId());
            if (CollUtil.isEmpty(apsDetailWeekdays1)) {
                continue;
            }

            Map<Integer, List<ApsDetailWeekday>> collect = apsDetailWeekdays1.stream().collect(Collectors.groupingBy(ApsDetailWeekday::getApsWorkDetailId));

            Set<Map.Entry<Integer, List<ApsDetailWeekday>>> entries = collect.entrySet();

            List<ApsTask> l = new ArrayList<>();
            for (Map.Entry<Integer, List<ApsDetailWeekday>> entry : entries) {
                ApsWorkDetail apsWorkDetail = detailMap.get(entry.getKey());

                entry.getValue().sort(Comparator.comparing(ApsDetailWeekday::getPlanStartDateTime));
                if(apsWorkDetail == null){
                    apsWorkDetail = new ApsWorkDetail();
                }
                apsWorkDetail.setPlanStartDateTime(entry.getValue().get(0).getPlanStartDateTime());
                apsWorkDetail.setPlanEndDateTime(entry.getValue().get(entry.getValue().size() - 1).getPlanEndDateTime());

                ApsTask task = new ApsTask(apsWorkDetail);
                l.add(task);

            }

            l.sort(Comparator.comparing(ApsTask::getPlanStartDateTime));
            ApsTask first = null;
            ApsTask last = null;
            if(!l.isEmpty()){
                first = l.get(0);
//                last = l.get(0);
            }

            for (ApsTask task : l) {
                if(last != null){
                    last.setNextTask(task);
                }
                last = task;

            }

            if(last != null){
                last.setNextTask(machine.getNextTask());
            }

            if(first != null){
                machine.setNextTask(first);
            }



        }
    }
}
